<template>
    <div class="lazy-cascader" :style="{ width: width }">
      <!-- 禁用状态 -->
      <div
          v-if="disabled"
          class="el-input__inner lazy-cascader-input lazy-cascader-input-disabled"
      >
        <span class="lazy-cascader-placeholder" v-show="placeholderVisible">
          {{ placeholder }}
        </span>
        <div class="lazy-cascader-tags" v-if="props.multiple">
          <el-tag
              class="lazy-cascader-tag"
              type="info"
              disable-transitions
              v-for="(item, index) in labelArray"
              :key="index"
              closable
          >
            <span> {{ item.label.join(separator) }}</span>
          </el-tag>
        </div>
        <div class="lazy-cascader-label" v-else>
          <el-tooltip
              placement="top-start"
              :content="labelObject.label.join(separator)"
          >
            <span>{{ labelObject.label.join(separator) }}</span>
          </el-tooltip>
        </div>
      </div>
      <!-- 可选状态 -->
      <el-popover v-else trigger="click" placement="bottom-start" ref="popover">
        <!-- 级联面板 -->
        <div class="lazy-cascader-panel">
          <el-cascader-panel
              ref="panel"
              v-model="current"
              :options="options"
              :props="currentProps"
              @change="change"
          ></el-cascader-panel>
        </div>
        <!-- 级联面板 -->
        <!--内容区域-->
        <div
            class="el-input__inner lazy-cascader-input"
            :class="disabled ? 'lazy-cascader-input-disabled' : ''"
            slot="reference"
        >
          <span class="lazy-cascader-placeholder" v-show="placeholderVisible">
            {{ placeholder }}
          </span>
          <div class="lazy-cascader-tags" v-if="props.multiple">
            <el-tag
                class="lazy-cascader-tag"
                type="info"
                size="small"
                disable-transitions
                v-for="(item, index) in labelArray"
                :key="index"
                closable
                @close="handleClose(item)"
            >
              <span> {{ item.label.join(separator) }}</span>
            </el-tag>
          </div>
          <div class="lazy-cascader-label" v-else>
            <el-tooltip
                placement="top-start"
                :content="labelObject.label.join(separator)"
            >
              <span>{{ labelObject.label.join(separator) }}</span>
            </el-tooltip>
          </div>
          <span
              class="lazy-cascader-clear"
              v-if="clearable && current.length > 0"
              @click.stop="clearBtnClick"
          >
            <i class="el-icon-close"></i>
          </span>
        </div>
      </el-popover>
    </div>
  </template>
  <script>

/* LazyCascader:

1.组件接受一系列的 props 参数，用于配置组件的行为和样式，例如默认值、分隔符、占位符、宽度等。
2.组件根据禁用状态和可选状态来渲染不同的视图。如果组件处于禁用状态，将显示已选择项的标签或文本。如果是多选模式，会生成多个 el-tag 标签，否则会显示一个带有 tooltip 的标签。
3.如果组件处于可选状态，会使用 el-popover 实现一个下拉面板，其中嵌套了 el-cascader-panel 组件，用于选择多级选项。
4.currentProps 对象中包含了一些属性，用于配置级联选择器的行为，包括是否多选、是否严格模式、选择的值、标签、叶子节点、懒加载等。
5.在组件创建时会调用 initOptions 方法，根据懒加载的逻辑初始化选项。
6.当 current（当前选中的值）发生变化时，会调用 getLabelArray 方法来获取已选项的标签数据。
7.getObject 方法根据给定的 ID，递归地从选项列表中找到对应的对象，并构造出一个包含 value 和 label 的对象。
8.lazyLoad 方法实现了懒加载的逻辑。当加载一个节点的子节点数据时，会将数据放入 node.data.children，并根据多选状态来更新当前选中的值。 */
  export default {
    props: {
      value: {
        type: Array,
        default: () => {
          return [];
        }
      },
      // 分隔符
      separator: {
        type: String,
        default: " / "
      },
      placeholder: {
        type: String,
        default: "请选择"
      },
      width: {
        type: String,
        default: "400px"
      },
      clearable: Boolean,
      disabled: Boolean,
      props: {
        type: Object,
        default: () => {
          return {};
        }
      }
    },
    data() {
      return {
        options: [], // 级联数据
        current: [], // 当前选中的值
        labelObject: {label: [], value: []}, // 单选时的label
        labelArray: [], // 多选时的label
        currentProps: { // 级联属性
          multiple: this.props.multiple, // 是否多选
          checkStrictly: this.props.checkStrictly, // 是否严格的遵守父子节点不互相关联
          value: this.props.value, // 选中的值
          label: this.props.label, // 选中的label
          leaf: this.props.leaf, // 是否是叶子节点
          lazy: true, // 是否懒加载
          lazyLoad: this.lazyLoad // 懒加载方法
        }
      };
    },
    computed: {
      // 是否显示placeholder
      placeholderVisible() {
        if (this.current) {
          return this.current.length == 0;
        } else {
          return true;
        }
      }
    },
    watch: {
      //当 current（当前选中的值）发生变化时，会调用 getLabelArray 方法来获取已选项的标签数据。
      current() {
        this.getLabelArray();
      },
      // 当 value（当前选中的值）发生变化时，会将 value 赋值给 current（当前选中的值）。
      value(v) {
        this.current = v;
      }
    },
    // 当组件创建时，会调用 initOptions 方法来初始化级联数据。
    created() {
      this.initOptions();
    },
    methods: {
      //初始化数据
      async initOptions() {
        // 调用 lazyLoad 方法来获取级联数据
        this.props.lazyLoad(0, list => {
          this.$set(this, "options", list);
          if (this.props.multiple) {
            this.current = [...this.value];
          } else {
            this.current = this.value;
          }
        });
      },
      // 获取已选项的标签数据
      async getLabelArray() {
        // 如果是多选，那么就遍历 current（当前选中的值），然后调用 getObject 方法来获取每个选中项的标签数据，最后将标签数据赋值给 labelArray。
        if (this.props.multiple) {
          let array = [];
          for (let i = 0; i < this.current.length; i++) {
            let obj = await this.getObject(this.current[i]);
            array.push(obj);
          }
          this.labelArray = array;
          this.$emit("input", this.current);
          if (!this.disabled) {
            this.$nextTick(() => {
              this.$refs.popover.updatePopper();
            });
          }
        } else {
          // 如果是单选，那么就调用 getObject 方法来获取当前选中项的标签数据，最后将标签数据赋值给 labelObject。
          this.labelObject = await this.getObject(this.current || []);
          this.$emit("input", this.current);
        }
      },
      /**格式化id=>object */
      // getObject 方法根据给定的 ID，递归地从选项列表中找到对应的对象，并构造出一个包含 value 和 label 的对象。
      async getObject(id) {
        try {
          let options = this.options;
          let nameArray = [];
          for (let i = 0; i < id.length; i++) {
            let index = options.findIndex(item => {
              return item[this.props.value] == id[i];
            });
            nameArray.push(options[index][this.props.label]);
            // 如果不是最后一个 ID，那么就判断当前节点是否有 children 属性，如果没有，那么就调用 lazyLoad 方法来获取当前节点的子节点数据，然后将子节点数据赋值给当前节点的 children 属性。
            if (i < id.length - 1 && options[index].children == undefined) {
              let list = new Promise(resolve => {
                this.props.lazyLoad(id[i], list => {
                  resolve(list);
                });
              });
              // this.$set 方法是 Vue 提供的，用来给对象添加属性，这里用来给当前节点添加 children 属性。
              this.$set(options[index], "children", await list);
              options = options[index].children;
            } else {
              options = options[index].children;
            }
          }
          return {value: id, label: nameArray};
        } catch (e) {
          this.current = [];
          return {value: [], label: []};
        }
      },
      //懒加载数据
      async lazyLoad(node, resolve) {
        let current = this.current;
        if (this.props.multiple) {
          current = [...this.current];
        }
        // 如果是根节点，那么就直接调用 resolve 方法来结束懒加载。
        if (node.root) {
          resolve();
        } else if (node.data[this.props.leaf]) {
          // 如果是叶子节点，那么就直接调用 resolve 方法来结束懒加载。
          resolve();
        } else if (node.data.children) {
          // 如果已经有子节点了，那么就直接调用 resolve 方法来结束懒加载。
          if (this.props.multiple) {
            // 如果是多选，那么就将当前选中的值赋值给 current。
            this.current = current;
          }
          resolve();
        } else {
          // 如果没有子节点，那么就调用 lazyLoad 方法来获取子节点数据。
          this.props.lazyLoad(node.value, list => {
            node.data.children = list;
            if (this.props.multiple) {
              this.current = current;
            }
            resolve(list);
          });
        }
      },
      //删除多选值
      /**删除**/
      handleClose(item) {
        // 通过 findIndex 方法找到当前选中项在 current（当前选中的值）中的索引，然后调用 splice 方法来删除当前选中项。
        let index = this.current.findIndex(obj => {
          return obj.join() == item.value.join();
        });
        if (index > -1) {
          this.$refs.panel.clearCheckedNodes();
          this.current.splice(index, 1);
          this.$emit("change", this.current);
        }
      },
      //点击清空按钮
      clearBtnClick() {
        // 调用 clearCheckedNodes 方法来清空选中项，然后将 current（当前选中的值）赋值为空数组。
        this.$refs.panel.clearCheckedNodes();
        this.current = [];
        this.$emit("change", this.current);
      },
      change() {
        this.$emit("change", this.current);
      }
    }
  };
  </script>
  <style lang="less">
  .lazy-cascader {
    display: inline-block;
    width: 300px;
  
    .lazy-cascader-input {
      position: relative;
      width: 100%;
      background: #fff;
      height: auto;
      min-height: 36px;
      padding: 5px;
      line-height: 1;
      cursor: pointer;
  
      > .lazy-cascader-placeholder {
        padding: 0 2px;
        line-height: 28px;
        color: #999;
        font-size: 14px;
      }
  
      > .lazy-cascader-label {
        padding: 0 2px;
        line-height: 28px;
        color: #606266;
        font-size: 14px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
  
      > .lazy-cascader-clear {
        position: absolute;
        right: 0;
        top: 0;
        display: inline-block;
        width: 40px;
        height: 40px;
        text-align: center;
        line-height: 40px;
      }
    }
  
    .lazy-cascader-input-disabled {
      background-color: #f5f7fa;
      border-color: #e4e7ed;
      color: #c0c4cc;
      cursor: not-allowed;
  
      > .lazy-cascader-label {
        color: #c0c4cc;
      }
  
      > .lazy-cascader-placeholder {
        color: #c0c4cc;
      }
    }
  }
  
  .lazy-cascader-tag {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    margin: 2px;
    text-overflow: ellipsis;
    background: #f0f2f5;
  
    > span {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  
    > .el-icon-close {
      -webkit-box-flex: 0;
      -ms-flex: none;
      flex: none;
      background-color: #c0c4cc;
      color: #fff;
    }
  }
  
  .lazy-cascader-panel {
    margin: -12px;
    display: inline-block;
  }
  </style>